﻿//////////////////////////////////////////////////////////////////////////////////////////////////
//   Project:  Rights Management Template Sync
//   Website:  http://rightsmanagement.codeplex.com/
// File Name:  Code.cs
//   Created:  01/06/2009
//  Modified:  01/06/2009
//    Author:  Andrew Willett
//   Website:  http://www.andrewwillett.com/
//   License:  Microsoft Public License (Ms-PL)
//             http://www.opensource.org/licenses/ms-pl.html
//             Open Source Software
//             The above license governs use of the this software. 
//             If you use the software, you accept this license. 
//             If you do not accept the license, do not use the software.
//             This notice must not be removed.
///////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.IO;
using System.Net;
using System.Text;
using CodePlex.RightsManagement.TemplateSync.Properties;
using CodePlex.RightsManagement.TemplateSync.WebService;
using Microsoft.Win32;
using System.Security;

namespace CodePlex.RightsManagement.TemplateSync
{
    /// <summary>
    /// 
    /// </summary>
    internal static class Code
    {
        /// <summary>
        /// Synchronises the templates on the Active Directory Rights Management Services (AD-RMS) cluster with the local user store.
        /// </summary>
        /// <returns>A System.Boolean value that represents whether the operation was successful.</returns>
        internal static Boolean SyncTemplates()
        {
            String clusterUri;
            String templateLocation;
            Dictionary<Guid, String> clusterTemplateHashes;
            Dictionary<Guid, String> localTemplateHashes;
            Dictionary<Guid, Delta> templateDeltas;
            clusterUri =
                GetClusterUri();
            if (clusterUri == null)
            {
                AddLogEntry(
                    "The URI of the Active Directory Rights Management Services cluster could not be determined.",
                    LogLevel.Error);
                return false;
            }
            templateLocation =
                GetTemplateLocation();
            clusterTemplateHashes =
                GetClusterTemplateHashes(
                    clusterUri);
            if (clusterTemplateHashes == null)
            {
                AddLogEntry(
                    "Could not connect to the Active Directory Rights Management Services cluster.",
                    LogLevel.Error);
                return false;
            }
            localTemplateHashes =
                GetLocalTemplateHashes(
                    templateLocation);
            templateDeltas =
                CalculateTemplateDeltas(
                localTemplateHashes,
                clusterTemplateHashes);
            ProcessTemplateDeltas(
                templateDeltas,
                localTemplateHashes,
                clusterUri,
                templateLocation);
            return true;
        }
        /// <summary>
        /// Attempts to get the URI of the AD-RMS cluster.
        /// </summary>
        /// <returns>Returns a System.String that represents the URI of the AD-RMS cluster, or null</returns>
        private static String GetClusterUri()
        {
            AddLogEntry(
                "Attempting to get the URI of the AD-RMS cluster...",
                LogLevel.Verbose);
            String uri;
            uri = null;
            uri =
                GetClusterUriFromServiceConnectionPoint();
            if (uri != null)
            {
                AddLogEntry(
                    "The following URI of the AD-RMS cluster was retrieved: " + uri,
                    LogLevel.Verbose);
                return uri;
            }
            String registryLocation;
            registryLocation =
                GetRegistryLocation();
            if (registryLocation != null)
            {
                uri =
                    GetClusterUriFromRegistry(
                        registryLocation);
                if (uri != null)
                {
                    AddLogEntry(
                        "The following URI of the AD-RMS cluster was retrieved: " + uri,
                        LogLevel.Verbose);
                    return uri;
                }
            }
            AddLogEntry(
                "Failed to get the URI of the AD-RMS cluster.",
                LogLevel.Verbose);
            return uri;
        }
        /// <summary>
        /// Attempts to get the URI of the AD-RMS cluster from the Service Connection Point (SCP) in Active Directory (AD).
        /// </summary>
        /// <returns>Returns a System.String that represents the URI of the AD-RMS cluster from the SCP in AD, or null.</returns>
        private static String GetClusterUriFromServiceConnectionPoint()
        {
            AddLogEntry(
                "Attempting to get the URI of the AD-RMS cluster from the Service Connection Point (SCP) in Active Directory (AD)...",
                LogLevel.Verbose);
            DirectoryEntry serviceConnectionPoint;
            String defaultNamingContext;
            String uri;
            uri = null;
            try
            {
                using (DirectoryEntry rootDSE = new DirectoryEntry("LDAP://RootDSE"))
                    defaultNamingContext = rootDSE.Properties["defaultNamingContext"].Value.ToString();
                serviceConnectionPoint =
                    new DirectoryEntry(
                        "LDAP://" +
                        Settings.Default.ScpLocation +
                        "," +
                        defaultNamingContext);
                serviceConnectionPoint.AuthenticationType = AuthenticationTypes.Secure;
                uri = serviceConnectionPoint.Properties[Settings.Default.ScpDirectoryAttribute].Value.ToString();
                uri =
                    ConvertServiceUri(
                        uri);
                serviceConnectionPoint.Close();
            }
            catch (Exception exception)
            {
                AddLogEntry(
                    "Querying AD for the SCP of the AD-RMS cluster failed with the following error: " + exception.Message,
                    LogLevel.Verbose);
            }
            if (uri == null)
                AddLogEntry(
                    "Failed to get the URI of the AD-RMS cluster from the SCP in AD.",
                    LogLevel.Verbose);
            else
                AddLogEntry(
                    "The following URI of the AD-RMS cluster was retrieved from the SCP in AD: " + uri,
                    LogLevel.Verbose);
            return uri;
        }
        /// <summary>
        /// Attempts to get the Windows registry path where RMS client information is stored from the list of known compatible client locations.
        /// </summary>
        /// <returns>Returns a System.String that represents the Windows registry path where RMS client information is stored, or null.</returns>
        private static String GetRegistryLocation()
        {
            AddLogEntry(
                "Attempting to get the Windows registry path where RMS client information is stored from the list of known compatible client locations...",
                LogLevel.Verbose);
            String registryLocation;
            registryLocation = null;
            foreach (String _registryLocation in Settings.Default.RegistryLocations)
            {
                AddLogEntry(
                    "Searching " + _registryLocation + "...",
                    LogLevel.Verbose);
                String value;
                value = null;
                try
                {
                    value =
                        (String)Registry.GetValue(
                            _registryLocation,
                            Settings.Default.TemplateLocationKeyName,
                            null);
                }
                catch (Exception exception)
                {
                    AddLogEntry(
                        "Searching this registry location failed with the following error: " + exception.Message,
                        LogLevel.Verbose);
                }
                if (value != null)
                {
                    registryLocation = _registryLocation;
                    break;
                }
            }
            if (registryLocation == null)
                AddLogEntry(
                    "Failed to get the Windows registry path.",
                    LogLevel.Verbose);
            else
                AddLogEntry(
                    "The following Windows registry path was retrieved: " + registryLocation,
                    LogLevel.Verbose);
            return registryLocation;
        }
        /// <summary>
        /// Attempts to get the URI of the AD-RMS cluster from the Windows registry.
        /// </summary>
        /// <param name="RegistryLocation">The registry path where RMS client configuration is stored.</param>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <returns>Returns a System.String that represents the URI of the AD-RMS cluster from the Windows registry, or null.</returns>
        private static String GetClusterUriFromRegistry(
            String RegistryLocation)
        {
            AddLogEntry(
                "Attempting to get the URI of the AD-RMS cluster from the Windows registry.",
                LogLevel.Verbose);
            AddLogEntry(
                "Registry location: " + RegistryLocation,
                LogLevel.Verbose);
            String registryUri;
            registryUri = null;
            foreach (String clusterLocationKeyName in Settings.Default.ClusterLocationKeyNames)
            {
                AddLogEntry(
                    "Querying " + clusterLocationKeyName + "...",
                    LogLevel.Verbose);
                String value;
                value = null;
                try
                {
                    value =
                        (String)Registry.GetValue(
                            RegistryLocation,
                            clusterLocationKeyName,
                            null);
                }
                catch (Exception exception)
                {
                    AddLogEntry(
                        "Querying this key name failed with the following error: " + exception.Message,
                        LogLevel.Verbose);
                }
                if (value != null)
                {
                    AddLogEntry(
                        "The following value was found: " + value,
                        LogLevel.Verbose);
                    String converted;
                    converted =
                        ConvertServiceUri(
                            value);
                    if (converted != null)
                    {
                        registryUri = converted;
                        break;
                    }
                    else
                    {
                        AddLogEntry(
                            "The following AD-RMS cluster found could not be converted into the service URI: " + value,
                            LogLevel.Warning);
                    }
                }
            }
            if (registryUri == null)
                AddLogEntry(
                    "Failed to get the URI of the AD-RMS cluster from the Windows registry.",
                    LogLevel.Verbose);
            else
                AddLogEntry(
                    "The following URI of the AD-RMS cluster was retrieved from the Windows registry: " + registryUri,
                    LogLevel.Verbose);
            return registryUri;
        }
        /// <summary>
        /// Gets the location of the local user store of templates.
        /// </summary>
        /// <returns>Returns a System.String that represents the location of the local user store of templates.</returns>
        private static String GetTemplateLocation()
        {
            AddLogEntry(
                "Getting the location of the local user store of templates",
                LogLevel.Verbose);
            String templateLocation;
            String registryLocation;
            registryLocation =
                GetRegistryLocation();
            if (registryLocation != null)
            {
                templateLocation =
                    GetTemplateLocationFromRegistry(
                        registryLocation);
                if (templateLocation != null)
                    return templateLocation;
            }
            templateLocation =
                GetTemplateLocationDefault();
            AddLogEntry(
                "The following location of the local user store of templates was found: " + templateLocation,
                LogLevel.Verbose);
            return templateLocation;
        }
        /// <summary>
        /// Gets the default location of the local user store of templates.
        /// </summary>
        /// <returns>Returns a System.String that represents the default location of the local user store of templates.</returns>
        private static String GetTemplateLocationDefault()
        {
            AddLogEntry(
                "Getting the default location of the local user store of templates.",
                LogLevel.Verbose);
            String templateDefaultLocation;
            templateDefaultLocation = Settings.Default.TemplateLocationDefault;
            AddLogEntry(
                "The following default location was found: " + templateDefaultLocation,
                LogLevel.Verbose);
            return templateDefaultLocation;
        }
        /// <summary>
        /// Attempts to get the location of the local user store of templates from the Windows registry.
        /// </summary>
        /// <param name="RegistryLocation">The registry path where RMS client configuration is stored.</param>
        /// <returns>Returns a System.String that represents the location of the local user store of templates from the Windows registry, or null.</returns>
        private static String GetTemplateLocationFromRegistry(
            String RegistryLocation)
        {
            String templateLocation;
            templateLocation = null;
            try
            {
                templateLocation =
                    ((String)Registry.GetValue(
                        RegistryLocation,
                        Settings.Default.TemplateLocationKeyName,
                        null))
                    .TrimEnd('\\');
            }
            catch { }
            return templateLocation;
        }
        /// <summary>
        /// Attempts to gets the list of template GUIDs and associated hashes present on the AD-RMS cluster.
        /// </summary>
        /// <param name="Uri">The URI of the AD-RMS cluster.</param>
        /// <returns>Returns a System.Collections.Generic.Dictionary&lt;System.Guid,System.String&gt; that represents the list of template GUIDs and associated hashes present on the AD-RMS cluster, or null.</returns>
        private static Dictionary<Guid, String> GetClusterTemplateHashes(
            String ClusterUri)
        {
            TemplateDistributionWebService templateDistributionWebService;
            TemplateInformation templateInformation;
            Dictionary<Guid, String> templateHashes;
            VersionData versionData;
            templateDistributionWebService =
                new TemplateDistributionWebService();
            templateDistributionWebService.Url = ClusterUri;
            templateDistributionWebService.Credentials = CredentialCache.DefaultCredentials;
            versionData =
                new VersionData();
            versionData.MinimumVersion = Settings.Default.MinimumVersion;
            versionData.MaximumVersion = Settings.Default.MaximumVersion;
            templateDistributionWebService.VersionDataValue = versionData;
            templateHashes = null;
            try
            {
                templateInformation =
                    templateDistributionWebService.AcquireTemplateInformation();
                foreach (GuidHash guidHash in templateInformation.GuidHash)
                    templateHashes.Add(
                        new Guid(
                            guidHash.Guid),
                        NormalizeTemplateHash(
                            guidHash.Hash));
                templateDistributionWebService.Dispose();
            }
            catch { }

            return templateHashes;
        }
        /// <summary>
        /// Retrieves the list of template GUIDs and associated hashes present in the local user store of templates.
        /// </summary>
        /// <param name="TemplateLocation">The location of the local user store of templates.</param>
        /// <returns>Returns a System.Collections.Generic.Dictionary&lt;System.Guid,System.String&gt; that represents the list of template GUIDs and associated hashes present in the local user store of templates.</returns>
        private static Dictionary<Guid, String> GetLocalTemplateHashes(
            String TemplateLocation)
        {
            Dictionary<Guid, String> localTemplateHashes;
            String[] files;
            localTemplateHashes =
                new Dictionary<Guid, String>();
            try
            {
                files =
                    Directory.GetFiles(
                        TemplateLocation,
                        "*." + Settings.Default.TemplateFileExtension);
            }
            catch (DirectoryNotFoundException)
            {
                return localTemplateHashes;
            }
            foreach (String file in files)
            {
                String contents;
                Boolean templateValid;
                contents =
                    File.ReadAllText(
                        file);
                templateValid = false;
                foreach (String validTemplateSchema in Settings.Default.ValidTemplateTags)
                    if (contents.Contains(validTemplateSchema))
                    {
                        templateValid = true;
                        break;
                    }
                if (templateValid)
                {
                    KeyValuePair<Guid, String> localTemplateHash;
                    String fileName;
                    fileName =
                        Path.GetFileNameWithoutExtension(
                            file);
                    try
                    {
                        localTemplateHash =
                            DecodeFileName(
                                fileName);
                        localTemplateHashes.Add(
                            localTemplateHash.Key,
                            localTemplateHash.Value);
                    }
                    catch (ArgumentException)
                    {
                        try
                        {
                            File.Delete(
                                file);
                        }
                        catch { }
                    }
                    catch
                    {
                        if (Settings.Default.DeleteUnmanagedTemplates)
                        {
                            try
                            {
                                File.Delete(
                                    file);
                            }
                            catch { }
                        }
                    }
                }
            }
            return localTemplateHashes;
        }
        /// <summary>
        /// Gets the full templates of the specified template GUIDs from the AD-RMS cluster.
        /// </summary>
        /// <param name="ClusterUri">The URI of the AD-RMS cluster.</param>
        /// <param name="TemplateGuids">The template GUIDs for which full templates will be retrieved.</param>
        /// <returns>Returns a System.Collections.Generic.List&lt;CodePlex.RightsManagement.TemplateSync.WebService.GuidTemplate&gt; that represents the full templates of the specified template GUIDs from the AD-RMS cluster.</returns>
        private static List<GuidTemplate> GetClusterTemplates(
            String ClusterUri,
            List<Guid> TemplateGuids)
        {
            TemplateDistributionWebService templateDistributionWebService;
            List<GuidTemplate> fullTemplates;
            VersionData versionData;
            List<String> templateRequests;
            Int32 processedRequests;
            Int32 maximumRequests;
            templateDistributionWebService =
                new TemplateDistributionWebService();
            templateDistributionWebService.Url = ClusterUri;
            templateDistributionWebService.Credentials = CredentialCache.DefaultCredentials;
            versionData =
                new VersionData();
            versionData.MinimumVersion = Settings.Default.MinimumVersion;
            versionData.MaximumVersion = Settings.Default.MaximumVersion;
            templateDistributionWebService.VersionDataValue = versionData;
            templateRequests =
                new List<String>();
            foreach (Guid guid in TemplateGuids)
                templateRequests.Add("{" + guid.ToString() + "}");
            fullTemplates =
                new List<GuidTemplate>();
            processedRequests = 0;
            maximumRequests = Settings.Default.MaximumTemplatesPerRequest;
            while (processedRequests != templateRequests.Count)
            {
                Int32 toRetrieve;
                if ((templateRequests.Count - processedRequests) > maximumRequests)
                    toRetrieve = maximumRequests;
                else
                    toRetrieve = (templateRequests.Count - processedRequests);
                List<GuidTemplate> templates;
                templates =
                    new List<GuidTemplate>(
                        templateDistributionWebService.AcquireTemplates(
                            templateRequests.GetRange(
                                processedRequests,
                                toRetrieve)
                                .ToArray()));
                fullTemplates.AddRange(
                    templates);
                processedRequests += toRetrieve;
            }
            return fullTemplates;
        }
        /// <summary>
        /// Decodes the serialised filename representation of a template and its associated hash.
        /// </summary>
        /// <param name="FileName">The filename to decode.</param>
        /// <exception cref="System.FormatException">The name of the file is not in a recognised format.</exception>
        /// <returns>Returns a System.Collections.Generic.KeyValuePair&lt;System.Guid,System.String&gt; that represents the template GUID and its associated hash.</returns>
        private static KeyValuePair<Guid, String> DecodeFileName(
            String FileName)
        {
            KeyValuePair<Guid, String> value;
            Guid guid;
            String hash;
            String[] terms;
            terms =
                FileName.Split(
                    '-');
            if ((terms.Length != 3) || (terms[0] != "TMP"))
                throw new FormatException();
            guid =
                DecodeGuid(
                    terms[2]);
            if (guid == Guid.Empty)
                throw new FormatException();
            hash = terms[1];
            value =
                new KeyValuePair<Guid, String>(guid, hash);
            return value;
        }
        /// <summary>
        /// Calculates the delta between the local templates and cluster templates based on their hashes.
        /// </summary>
        /// <param name="LocalTemplateHashes">The list of local template GUIDs and their hashes.</param>
        /// <param name="ClusterTemplateHashes">The list of cluster template GUIDs and their hashes.</param>
        /// <returns>Returns a System.Collections.Generic.Dictionary&lt;System.Guid,CodePlex.RightsManagement.TemplateSync.Delta&gt; that represents a list of all known template GUIDs across the local user store and the cluster, and their deltas.</returns>
        private static Dictionary<Guid, Delta> CalculateTemplateDeltas(
            Dictionary<Guid, String> LocalTemplateHashes,
            Dictionary<Guid, String> ClusterTemplateHashes)
        {
            Dictionary<Guid, String> localTemplateHashes;
            Dictionary<Guid, String> clusterTemplateHashes;
            Dictionary<Guid, Delta> templateDeltas;
            localTemplateHashes =
                new Dictionary<Guid, String>(
                    LocalTemplateHashes);
            clusterTemplateHashes =
                new Dictionary<Guid, String>(
                    ClusterTemplateHashes);
            templateDeltas =
                new Dictionary<Guid, Delta>();
            foreach (KeyValuePair<Guid, String> clusterTemplateHash in clusterTemplateHashes)
            {
                if (localTemplateHashes.ContainsKey(clusterTemplateHash.Key))
                {
                    if (localTemplateHashes[clusterTemplateHash.Key] == clusterTemplateHash.Value)
                        templateDeltas.Add(
                            clusterTemplateHash.Key,
                            Delta.None);
                    else
                        templateDeltas.Add(
                            clusterTemplateHash.Key,
                            Delta.Changed);
                }
            }
            foreach (KeyValuePair<Guid, Delta> templateDelta in templateDeltas)
            {
                localTemplateHashes.Remove(
                    templateDelta.Key);
                clusterTemplateHashes.Remove(
                    templateDelta.Key);
            }
            foreach (KeyValuePair<Guid, String> clusterTemplateHash in clusterTemplateHashes)
                templateDeltas.Add(
                    clusterTemplateHash.Key,
                    Delta.New);
            foreach (KeyValuePair<Guid, String> localTemplateHash in localTemplateHashes)
                templateDeltas.Add(
                    localTemplateHash.Key,
                    Delta.Removed);
            return templateDeltas;
        }
        /// <summary>
        /// Processes the list of template deltas by downloading from the cluster and creating, modifying or deleting templates in the local user store.
        /// </summary>
        /// <param name="TemplateDeltas">The list of template deltas to process.</param>
        /// <param name="LocalTemplateHashes">The list of local template hashes.</param>
        /// <param name="Uri">The URI of the AD-RMS cluster.</param>
        /// <param name="TemplateLocation">The location of the local user store of templates.</param>
        private static void ProcessTemplateDeltas(
            Dictionary<Guid, Delta> TemplateDeltas,
            Dictionary<Guid, String> LocalTemplateHashes,
            String ClusterUri,
            String TemplateLocation)
        {
            List<Guid> guids;
            List<GuidTemplate> guidTemplates;
            guids = new List<Guid>();
            foreach (KeyValuePair<Guid, Delta> templateDelta in TemplateDeltas)
                if ((templateDelta.Value == Delta.New) || (templateDelta.Value == Delta.Changed))
                    guids.Add(templateDelta.Key);
            guidTemplates = GetClusterTemplates(ClusterUri, guids);
            foreach (KeyValuePair<Guid, Delta> templateDelta in TemplateDeltas)
            {
                switch (templateDelta.Value)
                {
                    case Delta.None:
                        {
                            break;
                        }
                    case Delta.Changed:
                        {
                            DeleteTemplate(
                                templateDelta.Key,
                                LocalTemplateHashes[templateDelta.Key],
                                TemplateLocation);
                            WriteTemplate(
                                guidTemplates.Find(
                                    x => x.Guid == templateDelta.Key.ToString()),
                                TemplateLocation);
                            break;
                        }
                    case Delta.Removed:
                        {
                            DeleteTemplate(
                                templateDelta.Key,
                                LocalTemplateHashes[templateDelta.Key],
                                TemplateLocation);
                            break;
                        }
                    case Delta.New:
                        {
                            WriteTemplate(
                                guidTemplates.Find(
                                    x => x.Guid == templateDelta.Key.ToString()),
                                TemplateLocation);
                            break;
                        }
                }
            }
        }
        /// <summary>
        /// Writes the specified template to the local user store of templates.
        /// </summary>
        /// <param name="Template">The template to write.</param>
        /// <param name="TemplateLocation">The location of the local user store of templates.</param>
        private static void WriteTemplate(
            GuidTemplate Template,
            String TemplateLocation)
        {
            String filename;
            String path;
            filename = ConstructFilename(new Guid(Template.Guid), Template.Hash);
            path = TemplateLocation + "\\" + filename;
            try
            {
                Directory.CreateDirectory(TemplateLocation);
                File.WriteAllText(path.ToString(), Template.Template);
            }
            catch { }
        }
        /// <summary>
        /// Deletes the specified template from the local user store of templates.
        /// </summary>
        /// <param name="Guid">The GUID of the file to delete.</param>
        /// <param name="Hash">The hash of the file to delete.</param>
        /// <param name="TemplateLocation">The location of the local user store of templates.</param>
        private static void DeleteTemplate(
            Guid Guid,
            String Hash,
            String TemplateLocation)
        {
            String filename;
            String path;
            filename =
                ConstructFilename(
                    Guid,
                    Hash);
            path = TemplateLocation + "\\" + filename;
            try
            {
                File.Delete(
                    path);
            }
            catch { }
        }
        /// <summary>
        /// Encodes a GUID object into the encoded GUID string representation.
        /// </summary>
        /// <param name="Guid">The GUID object.</param>
        /// <returns>Returns a System.String that represents the encoded GUID string representation.</returns>
        private static String EncodeGuid(
            Guid Guid)
        {
            String value;
            value = Guid.ToString();
            value = value.Replace("-", ";k");
            value = value.Insert(0, "{");
            value = value.Insert(value.Length, "}");
            return value;
        }
        /// <summary>
        /// Attempts to decode an encoded GUID string representation into a GUID object.
        /// </summary>
        /// <param name="EncodedGuid">The encoded GUID string representation</param>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <returns>Returns a System.Guid that represents the decoded GUID object, or Guid.Empty.</returns>
        private static Guid DecodeGuid(
            String EncodedGuid)
        {
            String guid;
            Guid value;
            value = Guid.Empty;
            guid = EncodedGuid;
            guid = guid.Replace("{", "");
            guid = guid.Replace("}", "");
            guid = guid.Replace(";k", "");
            try
            {
                value =
                    new Guid(guid);
            }
            catch (ArgumentNullException) { }
            catch (FormatException) { }
            catch (OverflowException) { }
            return value;
        }
        /// <summary>
        /// Attempts to convert the licensing/certification URI of the cluster into the URI of the template distribution web service.
        /// </summary>
        /// <param name="ClusterUri">The URI of the AD-RMS cluster.</param>
        /// <returns>Returns a System.String that represents the URI of the template distribution web service, or null.</returns>
        private static String ConvertServiceUri(
            String ClusterUri)
        {
            String serviceUri;
            Int32 index;
            serviceUri = ClusterUri;
            index =
                serviceUri.IndexOf(
                    "/_wmcs",
                    StringComparison.CurrentCultureIgnoreCase);
            if (index == -1)
                return null;
            serviceUri =
                serviceUri.Substring(
                    0,
                    index);
            serviceUri += Settings.Default.WebServicePath;
            return serviceUri;
        }
        /// <summary>
        /// Constructs the file name of a template from its GUID and hash.
        /// </summary>
        /// <param name="Guid">The GUID of the template.</param>
        /// <param name="Hash">The hash of the template.</param>
        /// <exception cref="System.ArgumentOutOfRangeException"></exception>
        /// <exception cref="System.ArgumentException"></exception>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <returns>Returns a System.String that represents the filename of the template from the specified GUID and hash.</returns>
        private static String ConstructFilename(
            Guid Guid,
            String Hash)
        {
            StringBuilder path;
            String filename;
            path =
                new StringBuilder();
            path.Append("TMP-");
            path.Append(NormalizeTemplateHash(Hash));
            path.Append("-");
            path.Append(EncodeGuid(Guid));
            path.Append(".");
            path.Append(Settings.Default.TemplateFileExtension);
            filename =
                path.ToString();
            return filename;
        }
        /// <summary>
        /// Normalizes the template hash by removing characters which are invalid for a system file name.
        /// </summary>
        /// <param name="TemplateHash">The hash of the template.</param>
        /// <exception cref="System.ArgumentNullException">The TemplateHash parameter is null.</exception>
        /// <returns>Returns a System.String that represents the normalized template hash.</returns>
        private static String NormalizeTemplateHash(
            String TemplateHash)
        {
            String normalizedHash;
            normalizedHash = TemplateHash;
            foreach (Char character in Path.GetInvalidFileNameChars())
                normalizedHash =
                    normalizedHash.Replace(
                        character.ToString(),
                        "");
            return normalizedHash;
        }
        /// <summary>
        /// Adds instrumentation information into the log. This is currently sent to the console.
        /// </summary>
        /// <param name="Message">The message to log.</param>
        /// <param name="LogLevel">The level of instrumentation generated.</param>
        private static void AddLogEntry(String Message, LogLevel LogLevel)
        {
            LogLevel logLevel;
            logLevel = (LogLevel)Settings.Default.LogLevel;
            if (logLevel > LogLevel)
                return;
            Console.WriteLine(Message);
            Console.WriteLine();
        }
        /// <summary>
        /// The delta between a local template and a cluster template.
        /// </summary>
        private enum Delta
        {
            /// <summary>
            /// The template is present in the local user store but has been deleted from the cluster.
            /// </summary>
            Removed = -1,
            /// <summary>
            /// The template is present and up-to-date.
            /// </summary>
            None = 0,
            /// <summary>
            /// The template is present in the local user store but a different/newer version exists on the cluster.
            /// </summary>
            Changed = 1,
            /// <summary>
            /// The template is not present in the local user store but is present on the cluster.
            /// </summary>
            New = 2,
        }
        /// <summary>
        /// The level of instrumentation generated.
        /// </summary>
        private enum LogLevel
        {
            /// <summary>
            /// Useful for tracing or debugging purposes only.
            /// </summary>
            Verbose = 0,
            /// <summary>
            /// Information regarding the successful running of the application.
            /// </summary>
            Information = 1,
            /// <summary>
            /// Warning information which should be investigated, but did not result in an application failure.
            /// </summary>
            Warning = 2,
            /// <summary>
            /// An error which resulted in the application failing to complete the operation.
            /// </summary>
            Error = 3,
        }
    }
}
